Train models

Having explored the retail data, let’s fit some models to it. We’ll use the first 25.5 years of data for model training, and the remaining 10 years for training.

In addition, a nice feature of the model function is that it can fit models in parallel by leveraging the future and future.apply packages. Here, we use the multisession plan to create a background cluster of R processes for this purpose.

library(dplyr)
library(tsibbledata)
library(tsibble)
library(feasts)
library(fable)
library(future)

plan(multisession)

aus_retail_tr <- aus_retail %>%
    filter(Month <= yearmonth("2008 Dec"))
aus_retail_vl <- aus_retail %>%
    filter(Month > yearmonth("2008 Dec"))

mods <- model(aus_retail_tr,
    drift=NAIVE(log(Turnover) ~ drift()),
    sdrift=SNAIVE(log(Turnover) ~ drift()),
    ar=ARIMA(log(Turnover)),
    ets_auto=ETS(log(Turnover)),
    ets_fixed=ETS(log(Turnover) ~ error("A") + trend("A") + season("A")),
    .safely=FALSE
)
Warning in sqrt(diag(best$var.coef)): NaNs produced

Warning in sqrt(diag(best$var.coef)): NaNs produced
nrow(mods)
[1] 150

Note that there are 150 separate models for each of the above, corresponding to all observed combinations of state/territory and industry (not every industry is represented in each state). The ability to parallelise model training is thus very useful.

Let’s examine the resulting output, for one time series. The plotted output from autoplot includes the point forecasts along with the 80% and 95% prediction intervals, for each model. The actual turnover in this period is given by the black line.

library(ggplot2)

fcasts <- forecast(mods, new_data=aus_retail_vl)

fcasts %>%
    filter(Industry == "Food retailing", State == "New South Wales") %>%
    autoplot(data=aus_retail_vl) +
        theme(legend.position="bottom")

The main feature of this plot is that the drift model is almost comically bad. Not only does it fails to capture the seasonal pattern in the data, but it also severely overestimates the growth in turnover in the validation period.

We can redo the plot, but omitting this one model and using only the 80% prediction intervals:

fcasts %>%
    filter(Industry == "Food retailing", State == "New South Wales", .model != "drift") %>%
    autoplot(data=aus_retail_vl, level=80) +
        theme(legend.position="bottom")

This plot shows that, in fact, all of the models are systematically overestimating the growth in turnover (although the actual growth is still mostly within the prediction intervals). To see whether this is limited to this particular time series, we can also aggregate up the forecasts to the state level and plot them. There are a couple of warts to be aware of:

state_vl <- aus_retail_vl %>%
    group_by(State) %>%
    summarise(Turnover=sum(Turnover))

fcasts_state <- fcasts %>%
    filter(Month > yearmonth("2008 Dec"), .model != "drift") %>%
    group_by(State, .model) %>%
    summarise(Turnover=sum(Turnover)) %>%
    bind_rows(state_vl) %>%
    tsibble(key=c(State, .model), index=Month) %>%
    mutate(.model=ifelse(is.na(.model), ".response", .model))

fcasts_state_plot <- function(state)
{
    fcasts_state %>%
        filter(State == state) %>%
        select(-State) %>%
        autoplot(Turnover) +
            theme(legend.position="bottom") +
            scale_y_log10() +
            annotation_logticks() +
            ggtitle(state)
}

fcasts_state_plot("New South Wales")

fcasts_state_plot("Victoria")

fcasts_state_plot("Queensland")

fcasts_state_plot("South Australia")

fcasts_state_plot("Western Australia")

fcasts_state_plot("Tasmania")

fcasts_state_plot("Northern Territory")

fcasts_state_plot("Australian Capital Territory")

This shows that all of the forecasts are systematically overestimating the trend. What’s causing this? The reason is probably because of how we split the data into training and validation periods. The training data terminates at the end of 2008, which corresponds to the global financial crisis; conversely, the validation data starts at a point in which the economy is low and beginning to recover from the crisis.

Update models to 2013

To test this hypothesis, let’s refit the models, but this time with the training period extended to the end of 2013. The drift model is omitted as it is clearly inappropriate for the data.

aus_retail_2013_tr <- aus_retail %>%
    filter(Month <= yearmonth("2013 Dec"))
aus_retail_2013_vl <- aus_retail %>%
    filter(Month > yearmonth("2013 Dec"))

mods_2013 <- model(aus_retail_2013_tr,
    sdrift=SNAIVE(log(Turnover) ~ drift()),
    ar=ARIMA(log(Turnover)),
    ets_auto=ETS(log(Turnover)),
    ets_fixed=ETS(log(Turnover) ~ error("A") + trend("A") + season("A")),
    .safely=FALSE
)
Warning in sqrt(diag(best$var.coef)): NaNs produced

Warning in sqrt(diag(best$var.coef)): NaNs produced

Warning in sqrt(diag(best$var.coef)): NaNs produced

Warning in sqrt(diag(best$var.coef)): NaNs produced

Warning in sqrt(diag(best$var.coef)): NaNs produced
fcasts_2013 <- forecast(mods_2013, new_data=aus_retail_2013_vl)

fcasts_state_2013 <- fcasts_2013 %>%
    filter(Month > yearmonth("2013 Dec")) %>%
    group_by(State, .model) %>%
    summarise(Turnover=sum(Turnover)) %>%
    bind_rows(state_vl) %>%
    tsibble(key=c(State, .model), index=Month) %>%
    mutate(.model=ifelse(is.na(.model), ".response", .model))

fcasts_state_2013_plot <- function(state)
{
    fcasts_state_2013 %>%
        filter(State == state) %>%
        select(-State) %>%
        autoplot(Turnover) +
            theme(legend.position="bottom") +
            scale_y_log10() +
            annotation_logticks() +
            ggtitle(state)
}

fcasts_state_2013_plot("New South Wales")

fcasts_state_2013_plot("Victoria")

fcasts_state_2013_plot("Queensland")

fcasts_state_2013_plot("South Australia")

fcasts_state_2013_plot("Western Australia")

fcasts_state_2013_plot("Tasmania")

fcasts_state_2013_plot("Northern Territory")

fcasts_state_2013_plot("Australian Capital Territory")

The plots show much better agreement between forecasts and actuals, especially for the larger state (NSW and Victoria). Nevertheless, there is still substantial error for the smaller states. This is probably because these states were hit harder by the global recession and took longer to recover.

Measuring accuracy

A variety of point estimate accuracy measures are provided in the fabletools package. For this dataset, the MAPE (mean absolute percentage error) is appropriate. In general, you should not put too much emphasis on such measures as they play down the uncertainty inherent in any statistical inference task, let alone forecasting; remember to look at the prediction intervals as well to guide you on whether a model is adequate. Also, it’s better to treat these as relative measures, to help us decide which of a number of competing models to use, rather than looking at the absolute accuracy.

Nevertheless, let’s examine the MAPE scores of the different model types, both by state, and overall.

library(tidyr)

fcasts_2013_wide <- fcasts_2013 %>%
    filter(Month > yearmonth("2013 Dec")) %>%
    as_tibble() %>%
    pivot_wider(id_cols=c(State, Industry, .model, Month), names_from=.model, values_from=Turnover) %>%
    inner_join(aus_retail_2013_vl, by=c("State", "Industry", "Month"))

fcasts_2013_wide %>%
    group_by(State) %>%
    group_modify(function(.x, .y)
        summarise_at(.x, vars(sdrift:ets_fixed), function(x) MAPE(x - .x$Turnover, .x$Turnover))
    )
fcasts_2013_wide %>%
    summarise_at(vars(sdrift:ets_fixed), function(x) MAPE(x - .$Turnover, .$Turnover))

This broadly confirms the patterns seen in the plots above. The sdrift model performs worst, which is unsurprising given that it is simplistic by design. The ets_auto model performs best, probably because this particular dataset exhibits very clear trends and seasonal patterns. The forecast accuracy is best for the bigger states (NSW and Victoria) and worst for the Northern Territory and Western Australia.

Comment

There is a particularly timely and important observation to make regarding this dataset. From above, we saw that updating the models to use the data up to 2013 gave better forecast accuracy, especially for NSW and Victoria. Assuming that we were only interested in these two states, what would happen if we were to use the models to obtain forecasts for 2020 and beyond? Despite the good results on past data, they would almost certainly be very wide of the mark. This is because even the best model could not possibly anticipate the massive global recession caused by the COVID-19 pandemic. (Of course, the same would apply for any subjective forecast based on expert knowledge, so this is not an endorsement of judgemental forecasting.)

These results demonstrate the risks inherent in forecasting, especially when there is a strong trend. Even a trend that appears to be stable over time can diverge for reasons not captured in the data, resulting in systematic forecast errors. Any assessment of model performance should be interpreted in context, with the possibility of external shifts taken into account.

LS0tCnRpdGxlOiAiVXNpbmcgVGlkeXZlcnRzIHdpdGggdGhlIEF1c3RyYWxpYW4gcmV0YWlsIGRhdGE6IG1vZGVsbGluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgVHJhaW4gbW9kZWxzCgpIYXZpbmcgZXhwbG9yZWQgdGhlIHJldGFpbCBkYXRhLCBsZXQncyBmaXQgc29tZSBtb2RlbHMgdG8gaXQuIFdlJ2xsIHVzZSB0aGUgZmlyc3QgMjUuNSB5ZWFycyBvZiBkYXRhIGZvciBtb2RlbCB0cmFpbmluZywgYW5kIHRoZSByZW1haW5pbmcgMTAgeWVhcnMgZm9yIHRyYWluaW5nLgoKLSBgZHJpZnRgIGlzIGEgc2ltcGxlIHJhbmRvbSB3YWxrIG1vZGVsIGluY29ycG9yYXRpbmcgYSBkcmlmdCB0ZXJtLgotIGBzZHJpZnRgIGlzIHRoZSBzZWFzb25hbCBjb3VudGVycGFydCB0byBgZHJpZnRgLCBpZSB0aGUgcmFuZG9tIHdhbGsgaXMgYnkgc2Vhc29uLgotIGBhcmAgaXMgYW4gQVJJTUEgbW9kZWwgd2l0aCBhbGwgc2Vhc29uYWwgYW5kIG5vbnNlYXNvbmFsIHRlcm1zIGNob3NlbiBmcm9tIHRoZSBkYXRhLgotIGBldHNfYXV0b2AgaXMgYW4gRVRTIG1vZGVsIHdpdGggdGhlIGZvcm0gb2YgdGhlIGNvbXBvbmVudHMgY2hvc2VuIGZyb20gdGhlIGRhdGEgKGVpdGhlciBhZGRpdGl2ZSBvciBtdWx0aXBsaWNhdGl2ZSkuCi0gYGV0c19maXhlZGAgaXMgYW4gRVRTIG1vZGVsIHdoZXJlIHRoZSBjb21wb25lbnRzIGFyZSBhbGwgYWRkaXRpdmUsIGJhc2VkIG9uIGV4YW1pbmluZyB0aGUgcGxvdHMgaW4gdGhlIHByZXZpb3VzIG5vdGVib29rLgoKSW4gYWRkaXRpb24sIGEgbmljZSBmZWF0dXJlIG9mIHRoZSBgbW9kZWxgIGZ1bmN0aW9uIGlzIHRoYXQgaXQgY2FuIGZpdCBtb2RlbHMgaW4gcGFyYWxsZWwgYnkgbGV2ZXJhZ2luZyB0aGUgZnV0dXJlIGFuZCBmdXR1cmUuYXBwbHkgcGFja2FnZXMuIEhlcmUsIHdlIHVzZSB0aGUgYG11bHRpc2Vzc2lvbmAgcGxhbiB0byBjcmVhdGUgYSBiYWNrZ3JvdW5kIGNsdXN0ZXIgb2YgUiBwcm9jZXNzZXMgZm9yIHRoaXMgcHVycG9zZS4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRzaWJibGVkYXRhKQpsaWJyYXJ5KHRzaWJibGUpCmxpYnJhcnkoZmVhc3RzKQpsaWJyYXJ5KGZhYmxlKQpsaWJyYXJ5KGZ1dHVyZSkKCnBsYW4obXVsdGlzZXNzaW9uKQoKYXVzX3JldGFpbF90ciA8LSBhdXNfcmV0YWlsICU+JQogICAgZmlsdGVyKE1vbnRoIDw9IHllYXJtb250aCgiMjAwOCBEZWMiKSkKYXVzX3JldGFpbF92bCA8LSBhdXNfcmV0YWlsICU+JQogICAgZmlsdGVyKE1vbnRoID4geWVhcm1vbnRoKCIyMDA4IERlYyIpKQoKbW9kcyA8LSBtb2RlbChhdXNfcmV0YWlsX3RyLAogICAgZHJpZnQ9TkFJVkUobG9nKFR1cm5vdmVyKSB+IGRyaWZ0KCkpLAogICAgc2RyaWZ0PVNOQUlWRShsb2coVHVybm92ZXIpIH4gZHJpZnQoKSksCiAgICBhcj1BUklNQShsb2coVHVybm92ZXIpKSwKICAgIGV0c19hdXRvPUVUUyhsb2coVHVybm92ZXIpKSwKICAgIGV0c19maXhlZD1FVFMobG9nKFR1cm5vdmVyKSB+IGVycm9yKCJBIikgKyB0cmVuZCgiQSIpICsgc2Vhc29uKCJBIikpLAogICAgLnNhZmVseT1GQUxTRQopCgpucm93KG1vZHMpCmBgYAoKTm90ZSB0aGF0IHRoZXJlIGFyZSAxNTAgc2VwYXJhdGUgbW9kZWxzIGZvciBlYWNoIG9mIHRoZSBhYm92ZSwgY29ycmVzcG9uZGluZyB0byBhbGwgb2JzZXJ2ZWQgY29tYmluYXRpb25zIG9mIHN0YXRlL3RlcnJpdG9yeSBhbmQgaW5kdXN0cnkgKG5vdCBldmVyeSBpbmR1c3RyeSBpcyByZXByZXNlbnRlZCBpbiBlYWNoIHN0YXRlKS4gVGhlIGFiaWxpdHkgdG8gcGFyYWxsZWxpc2UgbW9kZWwgdHJhaW5pbmcgaXMgdGh1cyB2ZXJ5IHVzZWZ1bC4KCkxldCdzIGV4YW1pbmUgdGhlIHJlc3VsdGluZyBvdXRwdXQsIGZvciBvbmUgdGltZSBzZXJpZXMuIFRoZSBwbG90dGVkIG91dHB1dCBmcm9tIGBhdXRvcGxvdGAgaW5jbHVkZXMgdGhlIHBvaW50IGZvcmVjYXN0cyBhbG9uZyB3aXRoIHRoZSA4MCUgYW5kIDk1JSBwcmVkaWN0aW9uIGludGVydmFscywgZm9yIGVhY2ggbW9kZWwuIFRoZSBhY3R1YWwgdHVybm92ZXIgaW4gdGhpcyBwZXJpb2QgaXMgZ2l2ZW4gYnkgdGhlIGJsYWNrIGxpbmUuCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQoKZmNhc3RzIDwtIGZvcmVjYXN0KG1vZHMsIG5ld19kYXRhPWF1c19yZXRhaWxfdmwpCgpmY2FzdHMgJT4lCiAgICBmaWx0ZXIoSW5kdXN0cnkgPT0gIkZvb2QgcmV0YWlsaW5nIiwgU3RhdGUgPT0gIk5ldyBTb3V0aCBXYWxlcyIpICU+JQogICAgYXV0b3Bsb3QoZGF0YT1hdXNfcmV0YWlsX3ZsKSArCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQpgYGAKClRoZSBtYWluIGZlYXR1cmUgb2YgdGhpcyBwbG90IGlzIHRoYXQgdGhlIGBkcmlmdGAgbW9kZWwgaXMgYWxtb3N0IGNvbWljYWxseSBiYWQuIE5vdCBvbmx5IGRvZXMgaXQgZmFpbHMgdG8gY2FwdHVyZSB0aGUgc2Vhc29uYWwgcGF0dGVybiBpbiB0aGUgZGF0YSwgYnV0IGl0IGFsc28gc2V2ZXJlbHkgb3ZlcmVzdGltYXRlcyB0aGUgZ3Jvd3RoIGluIHR1cm5vdmVyIGluIHRoZSB2YWxpZGF0aW9uIHBlcmlvZC4KCldlIGNhbiByZWRvIHRoZSBwbG90LCBidXQgb21pdHRpbmcgdGhpcyBvbmUgbW9kZWwgYW5kIHVzaW5nIG9ubHkgdGhlIDgwJSBwcmVkaWN0aW9uIGludGVydmFsczoKCmBgYHtyfQpmY2FzdHMgJT4lCiAgICBmaWx0ZXIoSW5kdXN0cnkgPT0gIkZvb2QgcmV0YWlsaW5nIiwgU3RhdGUgPT0gIk5ldyBTb3V0aCBXYWxlcyIsIC5tb2RlbCAhPSAiZHJpZnQiKSAlPiUKICAgIGF1dG9wbG90KGRhdGE9YXVzX3JldGFpbF92bCwgbGV2ZWw9ODApICsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpCmBgYAoKVGhpcyBwbG90IHNob3dzIHRoYXQsIGluIGZhY3QsIF9hbGxfIG9mIHRoZSBtb2RlbHMgYXJlIHN5c3RlbWF0aWNhbGx5IG92ZXJlc3RpbWF0aW5nIHRoZSBncm93dGggaW4gdHVybm92ZXIgKGFsdGhvdWdoIHRoZSBhY3R1YWwgZ3Jvd3RoIGlzIHN0aWxsIG1vc3RseSB3aXRoaW4gdGhlIHByZWRpY3Rpb24gaW50ZXJ2YWxzKS4gVG8gc2VlIHdoZXRoZXIgdGhpcyBpcyBsaW1pdGVkIHRvIHRoaXMgcGFydGljdWxhciB0aW1lIHNlcmllcywgd2UgY2FuIGFsc28gYWdncmVnYXRlIHVwIHRoZSBmb3JlY2FzdHMgdG8gdGhlIHN0YXRlIGxldmVsIGFuZCBwbG90IHRoZW0uIFRoZXJlIGFyZSBhIGNvdXBsZSBvZiB3YXJ0cyB0byBiZSBhd2FyZSBvZjoKCi0gU29tZSB0aW1lIHNlcmllcyBhY3R1YWxseSBlbmQgYmVmb3JlIHRoZSB2YWxpZGF0aW9uIHBlcmlvZCwgc28gd2UgbmVlZCB0byBleGNsdWRlIHRoZW0gZnJvbSB0aGUgYWdncmVnYXRpb24gdG8gYXZvaWQgZGlzdG9ydGluZyB0aGUgcmVzdWx0cy4KLSBDdXJyZW50bHkgKGFzIG9mIE1heSAyMDIwKSB0aGVyZSBhcmUgc3RpbGwgc29tZSBnbGl0Y2hlcyB3aGVuIHJvdy1iaW5kaW5nIHRzaWJibGVzLCBzbyB3ZSBleHBsaWNpdGx5IGNvZXJjZSB0aGUgcmVzdWx0IG9mIGBiaW5kX3Jvd3MoKWAgdG8gYSB0c2liYmxlLgoKYGBge3J9CnN0YXRlX3ZsIDwtIGF1c19yZXRhaWxfdmwgJT4lCiAgICBncm91cF9ieShTdGF0ZSkgJT4lCiAgICBzdW1tYXJpc2UoVHVybm92ZXI9c3VtKFR1cm5vdmVyKSkKCmZjYXN0c19zdGF0ZSA8LSBmY2FzdHMgJT4lCiAgICBmaWx0ZXIoTW9udGggPiB5ZWFybW9udGgoIjIwMDggRGVjIiksIC5tb2RlbCAhPSAiZHJpZnQiKSAlPiUKICAgIGdyb3VwX2J5KFN0YXRlLCAubW9kZWwpICU+JQogICAgc3VtbWFyaXNlKFR1cm5vdmVyPXN1bShUdXJub3ZlcikpICU+JQogICAgYmluZF9yb3dzKHN0YXRlX3ZsKSAlPiUKICAgIHRzaWJibGUoa2V5PWMoU3RhdGUsIC5tb2RlbCksIGluZGV4PU1vbnRoKSAlPiUKICAgIG11dGF0ZSgubW9kZWw9aWZlbHNlKGlzLm5hKC5tb2RlbCksICIucmVzcG9uc2UiLCAubW9kZWwpKQoKZmNhc3RzX3N0YXRlX3Bsb3QgPC0gZnVuY3Rpb24oc3RhdGUpCnsKICAgIGZjYXN0c19zdGF0ZSAlPiUKICAgICAgICBmaWx0ZXIoU3RhdGUgPT0gc3RhdGUpICU+JQogICAgICAgIHNlbGVjdCgtU3RhdGUpICU+JQogICAgICAgIGF1dG9wbG90KFR1cm5vdmVyKSArCiAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgKwogICAgICAgICAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgICAgICAgICBhbm5vdGF0aW9uX2xvZ3RpY2tzKCkgKwogICAgICAgICAgICBnZ3RpdGxlKHN0YXRlKQp9CgpmY2FzdHNfc3RhdGVfcGxvdCgiTmV3IFNvdXRoIFdhbGVzIikKZmNhc3RzX3N0YXRlX3Bsb3QoIlZpY3RvcmlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIlF1ZWVuc2xhbmQiKQpmY2FzdHNfc3RhdGVfcGxvdCgiU291dGggQXVzdHJhbGlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIldlc3Rlcm4gQXVzdHJhbGlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIlRhc21hbmlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIk5vcnRoZXJuIFRlcnJpdG9yeSIpCmZjYXN0c19zdGF0ZV9wbG90KCJBdXN0cmFsaWFuIENhcGl0YWwgVGVycml0b3J5IikKYGBgCgpUaGlzIHNob3dzIHRoYXQgYWxsIG9mIHRoZSBmb3JlY2FzdHMgYXJlIHN5c3RlbWF0aWNhbGx5IG92ZXJlc3RpbWF0aW5nIHRoZSB0cmVuZC4gV2hhdCdzIGNhdXNpbmcgdGhpcz8gVGhlIHJlYXNvbiBpcyBwcm9iYWJseSBiZWNhdXNlIG9mIGhvdyB3ZSBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHBlcmlvZHMuIFRoZSB0cmFpbmluZyBkYXRhIHRlcm1pbmF0ZXMgYXQgdGhlIGVuZCBvZiAyMDA4LCB3aGljaCBjb3JyZXNwb25kcyB0byB0aGUgZ2xvYmFsIGZpbmFuY2lhbCBjcmlzaXM7IGNvbnZlcnNlbHksIHRoZSB2YWxpZGF0aW9uIGRhdGEgc3RhcnRzIGF0IGEgcG9pbnQgaW4gd2hpY2ggdGhlIGVjb25vbXkgaXMgbG93IGFuZCBiZWdpbm5pbmcgdG8gcmVjb3ZlciBmcm9tIHRoZSBjcmlzaXMuCgojIyBVcGRhdGUgbW9kZWxzIHRvIDIwMTMKClRvIHRlc3QgdGhpcyBoeXBvdGhlc2lzLCBsZXQncyByZWZpdCB0aGUgbW9kZWxzLCBidXQgdGhpcyB0aW1lIHdpdGggdGhlIHRyYWluaW5nIHBlcmlvZCBleHRlbmRlZCB0byB0aGUgZW5kIG9mIDIwMTMuIFRoZSBgZHJpZnRgIG1vZGVsIGlzIG9taXR0ZWQgYXMgaXQgaXMgY2xlYXJseSBpbmFwcHJvcHJpYXRlIGZvciB0aGUgZGF0YS4KCmBgYHtyfQphdXNfcmV0YWlsXzIwMTNfdHIgPC0gYXVzX3JldGFpbCAlPiUKICAgIGZpbHRlcihNb250aCA8PSB5ZWFybW9udGgoIjIwMTMgRGVjIikpCmF1c19yZXRhaWxfMjAxM192bCA8LSBhdXNfcmV0YWlsICU+JQogICAgZmlsdGVyKE1vbnRoID4geWVhcm1vbnRoKCIyMDEzIERlYyIpKQoKbW9kc18yMDEzIDwtIG1vZGVsKGF1c19yZXRhaWxfMjAxM190ciwKICAgIHNkcmlmdD1TTkFJVkUobG9nKFR1cm5vdmVyKSB+IGRyaWZ0KCkpLAogICAgYXI9QVJJTUEobG9nKFR1cm5vdmVyKSksCiAgICBldHNfYXV0bz1FVFMobG9nKFR1cm5vdmVyKSksCiAgICBldHNfZml4ZWQ9RVRTKGxvZyhUdXJub3ZlcikgfiBlcnJvcigiQSIpICsgdHJlbmQoIkEiKSArIHNlYXNvbigiQSIpKSwKICAgIC5zYWZlbHk9RkFMU0UKKQoKZmNhc3RzXzIwMTMgPC0gZm9yZWNhc3QobW9kc18yMDEzLCBuZXdfZGF0YT1hdXNfcmV0YWlsXzIwMTNfdmwpCgpmY2FzdHNfc3RhdGVfMjAxMyA8LSBmY2FzdHNfMjAxMyAlPiUKICAgIGZpbHRlcihNb250aCA+IHllYXJtb250aCgiMjAxMyBEZWMiKSkgJT4lCiAgICBncm91cF9ieShTdGF0ZSwgLm1vZGVsKSAlPiUKICAgIHN1bW1hcmlzZShUdXJub3Zlcj1zdW0oVHVybm92ZXIpKSAlPiUKICAgIGJpbmRfcm93cyhzdGF0ZV92bCkgJT4lCiAgICB0c2liYmxlKGtleT1jKFN0YXRlLCAubW9kZWwpLCBpbmRleD1Nb250aCkgJT4lCiAgICBtdXRhdGUoLm1vZGVsPWlmZWxzZShpcy5uYSgubW9kZWwpLCAiLnJlc3BvbnNlIiwgLm1vZGVsKSkKCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QgPC0gZnVuY3Rpb24oc3RhdGUpCnsKICAgIGZjYXN0c19zdGF0ZV8yMDEzICU+JQogICAgICAgIGZpbHRlcihTdGF0ZSA9PSBzdGF0ZSkgJT4lCiAgICAgICAgc2VsZWN0KC1TdGF0ZSkgJT4lCiAgICAgICAgYXV0b3Bsb3QoVHVybm92ZXIpICsKICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArCiAgICAgICAgICAgIHNjYWxlX3lfbG9nMTAoKSArCiAgICAgICAgICAgIGFubm90YXRpb25fbG9ndGlja3MoKSArCiAgICAgICAgICAgIGdndGl0bGUoc3RhdGUpCn0KCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIk5ldyBTb3V0aCBXYWxlcyIpCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIlZpY3RvcmlhIikKZmNhc3RzX3N0YXRlXzIwMTNfcGxvdCgiUXVlZW5zbGFuZCIpCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIlNvdXRoIEF1c3RyYWxpYSIpCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIldlc3Rlcm4gQXVzdHJhbGlhIikKZmNhc3RzX3N0YXRlXzIwMTNfcGxvdCgiVGFzbWFuaWEiKQpmY2FzdHNfc3RhdGVfMjAxM19wbG90KCJOb3J0aGVybiBUZXJyaXRvcnkiKQpmY2FzdHNfc3RhdGVfMjAxM19wbG90KCJBdXN0cmFsaWFuIENhcGl0YWwgVGVycml0b3J5IikKYGBgCgpUaGUgcGxvdHMgc2hvdyBtdWNoIGJldHRlciBhZ3JlZW1lbnQgYmV0d2VlbiBmb3JlY2FzdHMgYW5kIGFjdHVhbHMsIGVzcGVjaWFsbHkgZm9yIHRoZSBsYXJnZXIgc3RhdGUgKE5TVyBhbmQgVmljdG9yaWEpLiBOZXZlcnRoZWxlc3MsIHRoZXJlIGlzIHN0aWxsIHN1YnN0YW50aWFsIGVycm9yIGZvciB0aGUgc21hbGxlciBzdGF0ZXMuIFRoaXMgaXMgcHJvYmFibHkgYmVjYXVzZSB0aGVzZSBzdGF0ZXMgd2VyZSBoaXQgaGFyZGVyIGJ5IHRoZSBnbG9iYWwgcmVjZXNzaW9uIGFuZCB0b29rIGxvbmdlciB0byByZWNvdmVyLgoKCiMjIE1lYXN1cmluZyBhY2N1cmFjeQoKQSB2YXJpZXR5IG9mIHBvaW50IGVzdGltYXRlIGFjY3VyYWN5IG1lYXN1cmVzIGFyZSBwcm92aWRlZCBpbiB0aGUgZmFibGV0b29scyBwYWNrYWdlLiBGb3IgdGhpcyBkYXRhc2V0LCB0aGUgTUFQRSAobWVhbiBhYnNvbHV0ZSBwZXJjZW50YWdlIGVycm9yKSBpcyBhcHByb3ByaWF0ZS4gSW4gZ2VuZXJhbCwgeW91IHNob3VsZCBub3QgcHV0IHRvbyBtdWNoIGVtcGhhc2lzIG9uIHN1Y2ggbWVhc3VyZXMgYXMgdGhleSBwbGF5IGRvd24gdGhlIHVuY2VydGFpbnR5IGluaGVyZW50IGluIGFueSBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgdGFzaywgbGV0IGFsb25lIGZvcmVjYXN0aW5nOyByZW1lbWJlciB0byBsb29rIGF0IHRoZSBwcmVkaWN0aW9uIGludGVydmFscyBhcyB3ZWxsIHRvIGd1aWRlIHlvdSBvbiB3aGV0aGVyIGEgbW9kZWwgaXMgYWRlcXVhdGUuIEFsc28sIGl0J3MgYmV0dGVyIHRvIHRyZWF0IHRoZXNlIGFzIF9yZWxhdGl2ZV8gbWVhc3VyZXMsIHRvIGhlbHAgdXMgZGVjaWRlIHdoaWNoIG9mIGEgbnVtYmVyIG9mIGNvbXBldGluZyBtb2RlbHMgdG8gdXNlLCByYXRoZXIgdGhhbiBsb29raW5nIGF0IHRoZSBhYnNvbHV0ZSBhY2N1cmFjeS4KCiBOZXZlcnRoZWxlc3MsIGxldCdzIGV4YW1pbmUgdGhlIE1BUEUgc2NvcmVzIG9mIHRoZSBkaWZmZXJlbnQgbW9kZWwgdHlwZXMsIGJvdGggYnkgc3RhdGUsIGFuZCBvdmVyYWxsLgoKYGBge3J9CmxpYnJhcnkodGlkeXIpCgpmY2FzdHNfMjAxM193aWRlIDwtIGZjYXN0c18yMDEzICU+JQogICAgZmlsdGVyKE1vbnRoID4geWVhcm1vbnRoKCIyMDEzIERlYyIpKSAlPiUKICAgIGFzX3RpYmJsZSgpICU+JQogICAgcGl2b3Rfd2lkZXIoaWRfY29scz1jKFN0YXRlLCBJbmR1c3RyeSwgLm1vZGVsLCBNb250aCksIG5hbWVzX2Zyb209Lm1vZGVsLCB2YWx1ZXNfZnJvbT1UdXJub3ZlcikgJT4lCiAgICBpbm5lcl9qb2luKGF1c19yZXRhaWxfMjAxM192bCwgYnk9YygiU3RhdGUiLCAiSW5kdXN0cnkiLCAiTW9udGgiKSkKCmZjYXN0c18yMDEzX3dpZGUgJT4lCiAgICBncm91cF9ieShTdGF0ZSkgJT4lCiAgICBncm91cF9tb2RpZnkoZnVuY3Rpb24oLngsIC55KQogICAgICAgIHN1bW1hcmlzZV9hdCgueCwgdmFycyhzZHJpZnQ6ZXRzX2ZpeGVkKSwgZnVuY3Rpb24oeCkgTUFQRSh4IC0gLngkVHVybm92ZXIsIC54JFR1cm5vdmVyKSkKICAgICkKCmZjYXN0c18yMDEzX3dpZGUgJT4lCiAgICBzdW1tYXJpc2VfYXQodmFycyhzZHJpZnQ6ZXRzX2ZpeGVkKSwgZnVuY3Rpb24oeCkgTUFQRSh4IC0gLiRUdXJub3ZlciwgLiRUdXJub3ZlcikpCmBgYAoKVGhpcyBicm9hZGx5IGNvbmZpcm1zIHRoZSBwYXR0ZXJucyBzZWVuIGluIHRoZSBwbG90cyBhYm92ZS4gVGhlIGBzZHJpZnRgIG1vZGVsIHBlcmZvcm1zIHdvcnN0LCB3aGljaCBpcyB1bnN1cnByaXNpbmcgZ2l2ZW4gdGhhdCBpdCBpcyBzaW1wbGlzdGljIGJ5IGRlc2lnbi4gVGhlIGBldHNfYXV0b2AgbW9kZWwgcGVyZm9ybXMgYmVzdCwgcHJvYmFibHkgYmVjYXVzZSB0aGlzIHBhcnRpY3VsYXIgZGF0YXNldCBleGhpYml0cyB2ZXJ5IGNsZWFyIHRyZW5kcyBhbmQgc2Vhc29uYWwgcGF0dGVybnMuIFRoZSBmb3JlY2FzdCBhY2N1cmFjeSBpcyBiZXN0IGZvciB0aGUgYmlnZ2VyIHN0YXRlcyAoTlNXIGFuZCBWaWN0b3JpYSkgYW5kIHdvcnN0IGZvciB0aGUgTm9ydGhlcm4gVGVycml0b3J5IGFuZCBXZXN0ZXJuIEF1c3RyYWxpYS4KCiMjIyBDb21tZW50CgpUaGVyZSBpcyBhIHBhcnRpY3VsYXJseSB0aW1lbHkgYW5kIGltcG9ydGFudCBvYnNlcnZhdGlvbiB0byBtYWtlIHJlZ2FyZGluZyB0aGlzIGRhdGFzZXQuIEZyb20gYWJvdmUsIHdlIHNhdyB0aGF0IHVwZGF0aW5nIHRoZSBtb2RlbHMgdG8gdXNlIHRoZSBkYXRhIHVwIHRvIDIwMTMgZ2F2ZSBiZXR0ZXIgZm9yZWNhc3QgYWNjdXJhY3ksIGVzcGVjaWFsbHkgZm9yIE5TVyBhbmQgVmljdG9yaWEuIEFzc3VtaW5nIHRoYXQgd2Ugd2VyZSBvbmx5IGludGVyZXN0ZWQgaW4gdGhlc2UgdHdvIHN0YXRlcywgd2hhdCB3b3VsZCBoYXBwZW4gaWYgd2Ugd2VyZSB0byB1c2UgdGhlIG1vZGVscyB0byBvYnRhaW4gZm9yZWNhc3RzIGZvciAyMDIwIGFuZCBiZXlvbmQ/IERlc3BpdGUgdGhlIGdvb2QgcmVzdWx0cyBvbiBwYXN0IGRhdGEsIHRoZXkgd291bGQgYWxtb3N0IGNlcnRhaW5seSBiZSB2ZXJ5IHdpZGUgb2YgdGhlIG1hcmsuIFRoaXMgaXMgYmVjYXVzZSBldmVuIHRoZSBiZXN0IG1vZGVsIGNvdWxkIG5vdCBwb3NzaWJseSBhbnRpY2lwYXRlIHRoZSBtYXNzaXZlIGdsb2JhbCByZWNlc3Npb24gY2F1c2VkIGJ5IHRoZSBDT1ZJRC0xOSBwYW5kZW1pYy4gKE9mIGNvdXJzZSwgdGhlIHNhbWUgd291bGQgYXBwbHkgZm9yIGFueSBzdWJqZWN0aXZlIGZvcmVjYXN0IGJhc2VkIG9uIGV4cGVydCBrbm93bGVkZ2UsIHNvIHRoaXMgaXMgbm90IGFuIGVuZG9yc2VtZW50IG9mIGp1ZGdlbWVudGFsIGZvcmVjYXN0aW5nLikKClRoZXNlIHJlc3VsdHMgZGVtb25zdHJhdGUgdGhlIHJpc2tzIGluaGVyZW50IGluIGZvcmVjYXN0aW5nLCBlc3BlY2lhbGx5IHdoZW4gdGhlcmUgaXMgYSBzdHJvbmcgdHJlbmQuIEV2ZW4gYSB0cmVuZCB0aGF0IGFwcGVhcnMgdG8gYmUgc3RhYmxlIG92ZXIgdGltZSBjYW4gZGl2ZXJnZSBmb3IgcmVhc29ucyBub3QgY2FwdHVyZWQgaW4gdGhlIGRhdGEsIHJlc3VsdGluZyBpbiBzeXN0ZW1hdGljIGZvcmVjYXN0IGVycm9ycy4gQW55IGFzc2Vzc21lbnQgb2YgbW9kZWwgcGVyZm9ybWFuY2Ugc2hvdWxkIGJlIGludGVycHJldGVkIGluIGNvbnRleHQsIHdpdGggdGhlIHBvc3NpYmlsaXR5IG9mIGV4dGVybmFsIHNoaWZ0cyB0YWtlbiBpbnRvIGFjY291bnQuCgo=